import ArrayStack from "../stack";
import StackList from "../stack-list";
import { ruleParseList } from "./rule-parse";
import { pickHandleMap } from "./pick-handle";
import CatchBuffer from "./utils/catch-buffer";
import RuleIndexStack from "./utils/rule-index-stack";
import globelRuleParse from "./rule-parse/globelRuleParse";
import { RuleStack, RegExpInput, BranchStack, RuleParse, MatchFormat, CharHandle } from "./type";

export default class GaRegExp {

	public RegExpList: RuleStack = new StackList();

	public get RegExpLength(): number {
		return this.RegExpList.size;
	}

	/**
	 * 创建正则类
	 * @param input
	 */
	public constructor(input?: RegExpInput[]) {
		if (input) {
			this.ParseRule(input);
		}
	}

	public ParseRule(input: RegExpInput[]) {
		this.RegExpList = this.ParseRegExp(input);
	}

	/**
	 * 格式化正则语句
	 * @param input
	 */
	public ParseRegExp(input: RegExpInput[]): RuleStack {

		const parse = new ArrayStack<RuleParse>();

		const rule: RuleStack = new StackList();

		const length = input.length;
		let index = 0;

		while (index < length) {
			const info = input[index];
			if (typeof info === "string") {
				const length2 = info.length;
				let i2 = 0;
				while (i2 < length2) {
					const char = info[i2];
					this.ParseOneChar(parse, rule, char);
					i2++;
				}
			} else {
				this.ParseOneChar(parse, rule, info);
			}
			index++;
		}

		if (parse.size > 0 && parse.topCheck.parseType !== "globel") {
			this.ParseOneChar(parse, rule);
		}

		if (parse.size > 0 && parse.topCheck.parseType !== "globel") {
			throw new Error(`${parse.topCheck.parseType} 没有结尾`);
		}

		return rule;
	}

	public ParseOneChar(parse: ArrayStack<RuleParse>, rule: RuleStack, info?: RegExpInput): void {
		for (const ruleParse of ruleParseList) {
			ruleParse.preHandle(parse, rule, info);
		}
		const parseType = parse.top && parse.top.parseType;
		if (parseType === "globel") {
			rule.back();
			parse.pop();
		}

		if (info === undefined) {
			return;
		}

		const isGlobel = parse.top === undefined || parse.top.isNested;

		// insert
		if (info instanceof GaRegExp) {
			if (isGlobel) {
				rule.push({ ruleType: "nested", ruleData: info });
				parse.push(globelRuleParse);
			} else {
				throw new Error("嵌套插入位置错误");
			}
		}
		else if (typeof info === "object") {
			if (isGlobel) {
				rule.push(info);
				parse.push(globelRuleParse);
			} else {
				throw new Error("插入位置错误");
			}
		}
		else if (typeof info === "function") {
			if (isGlobel) {
				rule.push({ ruleType: "judge", ruleData: info });
				parse.push(globelRuleParse);
			} else {
				throw new Error("插入位置错误");
			}
		} else {
			const char = info;
			const isGlobel = parse.top === undefined || parse.top.isNested;
			if (isGlobel) {
				if (!ruleParseList.some(ruleParse => ruleParse.judge(parse, rule, char))) {
					if (parse.top === undefined || parse.top.isNested) {
						parse.push(globelRuleParse);
						rule.push({ ruleType: "equals", ruleData: char.charCodeAt(0) });
					}
				}
			} else {
				parse.topCheck.handle(parse, rule, char);
			}
		}

	}

	public static CopyBranchStack(branch: BranchStack): BranchStack {
		const { index } = branch;
		const ruleIndexStack = branch.ruleIndexStack.copy();
		const catchStack = branch.catchStack.copy();
		const catchBuffer = branch.catchBuffer.copy();
		const countStack = branch.countStack.copy();
		const onceStack = branch.onceStack.copy();
		const onceSet = branch.onceSet;
		return { index, ruleIndexStack, catchStack, catchBuffer, countStack, onceStack, onceSet };
	}

	public CopyBranchToSave(branch: BranchStack, save: Set<BranchStack>): BranchStack {
		const newBranch = GaRegExp.CopyBranchStack(branch);
		save.add(newBranch);
		return newBranch;
	}

	public PickHandle(pack: Set<BranchStack>, str: string, char: string, index: number, length: number): Set<BranchStack> {
		const finishs = new Set<BranchStack>();
		const save = new Set<BranchStack>(pack);
		pack.clear();
		while (save.size > 0) {
			for (const origin of Array.from(save)) {
				const { ruleIndexStack, countStack } = origin;
				const ruleInfo = ruleIndexStack.info;
				if (ruleInfo === undefined) {
					// on list end
					ruleIndexStack.back();
					if (ruleIndexStack.empty) {
						// on tree finish
						origin.index++;
						finishs.add(origin);
						save.delete(origin);
					} else {
						// exit list enter borther list
						const { ruleType, range } = ruleIndexStack.infoCheck.info;

						const count = range !== undefined && countStack.topCheck;
						const hasRange = range !== undefined;
						const inLess = range !== undefined && count < range.min;
						const inRange = range !== undefined && count >= range.min && count < range.max;

						/**  origin */
						const hasExit = !inLess;
						/** enterBranch */
						const hasReenter = inLess || inRange;

						const pickHandle = pickHandleMap[ruleType];
						if (!pickHandle.canCount) {
							// ruleIndexStack
							origin.ruleIndexStack.next();
							// pickHandle
							pickHandle.exit(origin, hasRange);
						} else {
							/** origin or new */
							const enterBranch = inRange ? this.CopyBranchToSave(origin, save) : origin;
							// countStack
							if (hasExit && hasRange) { origin.countStack.pop(); }
							if (hasReenter) { enterBranch.countStack.topCheck++; }
							// ruleIndexStack
							if (hasExit) { origin.ruleIndexStack.next(); }
							// pickHandle
							if (hasExit) { pickHandle.exit(origin, hasRange); }
							if (hasReenter) { pickHandle.reenter(enterBranch); }
						}
					}
				} else {
					// handle list item
					const { ruleType, range } = ruleInfo.info;

					const hasRange = range !== undefined;
					/** new branch exit */
					const hasEmpty = range !== undefined && range.min <= 0;

					const pickHandle = pickHandleMap[ruleType];
					if (!pickHandle.canCount) {
						pickHandle.onlyEnter(save, origin, str, index, char, length);
					} else {
						/** origin or new */
						const enterBranch = hasEmpty ? this.CopyBranchToSave(origin, save) : origin;
						// countStack
						if (hasRange) { origin.countStack.push(0); }
						// ruleIndexStack
						if (hasEmpty) { enterBranch.ruleIndexStack.next(); }
						// pickHandle
						if (pickHandle.isNode) {
							// count start for 0
							pack.add(origin);
							save.delete(origin);
						} else {
							if (hasEmpty) { pickHandle.newExit(enterBranch); }
							if (hasRange) { origin.countStack.topCheck++; }
							pickHandle.enter(origin, hasRange);
						}
					}
				}
			}
		}
		return finishs;
	}

	public PickHandleFinish(save: Set<BranchStack>, str: string, char: string, index: number, length: number): BranchStack | undefined {
		const finishs = this.PickHandle(save, str, char, index, length);
		if (finishs.size > 0) {
			if (finishs.size !== 1) {
				throw new Error("存在多分支满足条件");
			}
			return Array.from(finishs)[0];
		}
		return undefined;
	}

	public Execute(str: string) {

		const save = new Set<BranchStack>([
			{
				index: 0,
				ruleIndexStack: new RuleIndexStack([0], [this.RegExpList.origin]),
				catchBuffer: new CatchBuffer(10),
				catchStack: new ArrayStack(),
				countStack: new ArrayStack(),
				onceStack: new ArrayStack(),
				onceSet: new Set(),
			}
		]);
		const pick = new Set<BranchStack>();

		const length = str.length;
		let index = 0;

		const finishs = this.PickHandleFinish(save, str, str[index], index, length);
		if (finishs !== undefined) {
			throw new Error("存在有分支无条件匹配");
			return finishs;
		}
		save.forEach(origin => pick.add(origin));
		save.clear();

		while (index < length) {

			const char = str[index];
			let needWait = false;

			for (const origin of Array.from(pick)) {

				const { index: saveIndex, ruleIndexStack, catchStack, catchBuffer, countStack, onceStack, onceSet } = origin;
				const ruleInfo = ruleIndexStack.infoCheck;
				const { ruleType } = ruleInfo.info

				if (index > saveIndex) {
					throw new Error("分支序号不能小于处理序号");
				} else if (index < saveIndex) {
					continue;
				}

				const onceEmpty = onceStack.empty;
				const onceNot = !onceEmpty && onceSet.has(index);
				const res = onceNot ? false : pickHandleMap[ruleType].noodHnalde(pick, origin, ruleInfo, str, index, char, length);
				if (!onceEmpty && !onceNot) {
					onceSet.add(index);
				}

				const { range } = ruleInfo.info;
				const countInfo = countStack.top;

				const count = countInfo !== undefined && countInfo;
				const hasRange = range !== undefined;
				const inMax = range !== undefined && count < range.max;
				const inGreater = range !== undefined && !inMax;
				const inRange = range !== undefined && count > 0 && count >= range.min && count <= range.max;
				const inLess = inMax && !inRange;

				const isFailed = !hasRange ? res === false : (res === false && inLess);
				const isExit = !hasRange ? res === true : ((res === true && inGreater) || (res === false && inRange));
				const isRange = res === true && inMax;

				if (ruleType === "position") {
					needWait = true;
				}

				if (res === "next") {
					if (ruleType !== "position") {
						if (!catchStack.empty) {
							catchBuffer.push(char);
						}
					}
					origin.index++;
				} else if (res === "execute") {
					needWait = true;
					ruleIndexStack.next();
					save.add(origin);
					pick.delete(origin);
				} else {
					if (res === true && ruleType !== "position") {
						if (!catchStack.empty) {
							catchBuffer.push(char);
						}
						origin.index++;
					}

					if (isFailed) { pick.delete(origin); }
					else if (isRange) { countStack.topCheck++; }
					else if (isExit) {
						if (hasRange) { countStack.pop(); }
						if (inRange) { needWait = true; }
						ruleIndexStack.next();
						save.add(origin);
						pick.delete(origin);
					} else {
						debugger;
						throw new Error("逻辑书写错误");
					}
				}
			}

			if (!needWait) {
				index++;
			}
			const finishs = this.PickHandleFinish(save, str, str[index], index, length);
			if (finishs) {
				return finishs;
			}

			if (save.size === 0 && pick.size === 0) {
				return undefined;
			}

			// if (save.size > 30) {
			// 	console.log("分支过多", save.size, index);
			// }

			save.forEach(item => pick.add(item));
			save.clear();

		}

		if (pick.size > 0) {
			pick.forEach(info => {
				save.add(info);
			});
		}

		while (save.size > 0) {
			for (const origin of Array.from(save)) {

				const { ruleIndexStack, countStack } = origin;
				const ruleInfo = ruleIndexStack.infoCheck;
				const { ruleType, range } = ruleInfo.info;

				if (range === undefined) {
					const pickHandle = pickHandleMap[ruleType];
					if (ruleType === "position" && pickHandle.noodHnalde(save, origin, ruleInfo, str, index, str[index], length)) {
						ruleIndexStack.next();
					} else {
						save.delete(origin);
					}
				} else {
					const count = countStack.topCheck;
					if (range.min <= 0) {
						ruleIndexStack.next();
					} else if (count >= range.min && count <= range.max) {
						countStack.pop();
						ruleIndexStack.next();
					} else {
						save.delete(origin);
					}
				}
			}

			const finishs = this.PickHandleFinish(save, str, str[index], index, length);
			if (finishs) {
				return finishs;
			}
		}

		return undefined;
	}

	public Test(str: string): boolean {
		return this.Execute(str) !== undefined;
	}

	public Match(str: string) {
		const result = this.Execute(str);
		if (result === undefined) {
			return undefined;
		}
		return result.catchBuffer.Format();
	}

	public MatchFormat(str: string, ...a: any[]) {
		const result = this.Match(str);
		const jsonStr = JSON.stringify(result, ...a);
		return jsonStr;
	}

	public static Supply(origin: BranchStack, data: MatchFormat): void {
		const { catchBuffer } = origin;
		catchBuffer.Supply(data);
	}

	public Supply(data: MatchFormat): CharHandle {
		return (save, origin, rule, str, index, char, length) => {
			GaRegExp.Supply(origin, data);
			return "execute";
		};
	}

	public static GetLastCatch(origin: BranchStack): string {
		return origin.catchBuffer.GetLastCatch();
	}

}
